// Copyright (C) 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package generator

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"io/ioutil"
	"strings"

	"github.com/google/gapid/core/fault"
	"github.com/google/gapid/core/fault/cause"
	"github.com/google/gapid/core/log"
	"github.com/google/gapid/core/os/file"
)

type lingoFile struct {
	Input  file.Path
	Output file.Path
	Parsed *ast.File
}

const (
	inputSuffix        = ".lingo"
	outputSuffix       = ".go"
	ErrInvalidFilename = fault.Const("Invalid lingo input filename")
)

// RewriteFiles is called to generate parsing functionality.
// It takes a list of input lingo files, and generates go files of the same basename in the output path.
func RewriteFiles(ctx log.Context, base string, inputs ...string) error {
	info := &introspection{
		fset:   token.NewFileSet(),
		byName: map[string]*entry{},
	}
	files := []lingoFile{}
	basePath := file.Path{}
	if base != "" {
		basePath = file.Abs(base)
	}
	for _, name := range inputs {
		f := lingoFile{Input: file.Abs(name)}
		path, basename := f.Input.Split()
		if !basePath.IsEmpty() {
			path = basePath
		}
		if !strings.HasSuffix(name, inputSuffix) {
			return cause.Wrap(ctx, ErrInvalidFilename)
		}
		f.Output = path.Join(basename[:len(basename)-len(inputSuffix)] + outputSuffix)
		parsed, err := parser.ParseFile(info.fset, f.Input.System(), nil, parser.ParseComments)
		if err != nil {
			return err
		}
		f.Parsed = parsed
		if group := parsed.Comments[0]; group.Pos() < parsed.Package {
			group.List = append(group.List,
				&ast.Comment{Text: "// "},
				&ast.Comment{Text: "// Generated by lingo from " + basename},
			)
		}
		info.collectEntryPoints(ctx, f.Parsed)
		files = append(files, f)
	}
	info.prepare(ctx)
	info.rewrite(ctx)
	for _, f := range files {
		ctx := ctx.V("File", f.Output.Basename())
		buf := &bytes.Buffer{}
		formatErr := format.Node(buf, info.fset, f.Parsed)
		writeErr := ioutil.WriteFile(f.Output.System(), buf.Bytes(), 0666)
		if formatErr != nil {
			return cause.Explain(ctx, formatErr, "Formatting")
		}
		if writeErr != nil {
			return cause.Explain(ctx, writeErr, "Writing")
		}
	}
	return nil
}
